package parser;

import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;

import token.Token.Type;
import token.Tokenizer;
import valutatore.*;

public class Parser {

	private Tokenizer tokenizer;

	public Parser(Reader in) {
		this.tokenizer = new Tokenizer(in);
	}

	public Prog parse() throws IOException {
		next();
		// Prog ::= Fun Eos.
		Prog prog = fun();
		check(Type.Eos);
		return prog;
	}

	// Fun ::= "{" ( "(" Params? ")" )? Locals? ( "->" SeqExpr? )? "}".
	private Prog fun() throws IOException {
		ArrayList<Ident> params = new ArrayList<Ident>();
		ArrayList<Ident> locals = new ArrayList<Ident>();
		Expr exprs = null;
		
		if (!check(Type.OpenBrace))
			throw new ParserException("Mancata apertura graffa");
		next();
		if (check(Type.OpenParen))
			params = params();
//		next();
		locals = locals();
		if (check(Type.To)){
			next();
			exprs = seqExpr();
		}
		return new Prog(new Fun(params, locals, exprs));
	}

	private ArrayList<Ident> locals() throws IOException {
		ArrayList<Ident> locals = new ArrayList<Ident>();
		while (!check(Type.CloseBrace) && !check(Type.To)) {
			while (check(Type.Comma))
				next();
			if (check(Type.Ident))
				locals.add(new Ident(tokenizer.token().name()));
			else
				throw new ParserException("Non ho trovato un Ident");
		}
		return locals;
	}

	private ArrayList<Ident> params() throws IOException {
		ArrayList<Ident> params = new ArrayList<Ident>();
		next();
		while (!check(Type.CloseParen)) {
			while (check(Type.Comma))
				next();
			if (check(Type.Ident))
				params.add(new Ident(tokenizer.token().name()));
			else
				throw new ParserException("Non ho trovato un Ident");
		}
		return params;
	}

	// SeqExpr ::= ";"* AssignExpr (";" AssignExpr?)* .
	private Expr seqExpr() throws IOException {
		ArrayList<Expr> exprs = new ArrayList<Expr>();
		while (!check(Type.CloseBrace)) {
			while (check(Type.Semicolon))
				next();
			exprs.add(assignExpr());
			while (check(Type.Semicolon)) {
				next();
				if (!isInFollowsAssignExpr())
					exprs.add(assignExpr());
			}
			return exprs.size() == 1 ? exprs.get(0) : new SeqExpr(
					exprs.toArray(new Expr[exprs.size()]));
		}
		//errore da gestire
		System.out.println("Errore da gestire");
		return null;
	}

	private boolean check(Type type) throws IOException {
//		next();
		if (type() == type) {
			return true;
		} else {
			return false;
		}
	}

	// AssignExpr ::= Id AssignOp AssignExpr
	// | OrExpr
	// | epsilon.
	private Expr assignExpr() throws IOException {
		if (type() == Type.Ident) {
			String name = tokenizer.token().name();
			next();
			if (isAssignOP()) {
				Type oper = type();
				next();
				return new AssignExpr(new Ident(name), oper, assignExpr());
			}
			prev();
		}
		return orExpr();
		// oppure return epsilon???
	}

	// OrExpr ::= AndExpr ("||" AndExpr)*.
	private Expr orExpr() throws IOException {
		Expr left = andExpr();
		while (type() == Type.LogOr) {
			next();
			left = new BinaryExpr(left, TypeOp.Type.LogOr, orExpr());
		}
		return left;
	}

	// AndExpr ::= EqExpr ("&&" EqExpr)*
	private Expr andExpr() throws IOException {
		Expr left = eqExpr();
		while (type() == Type.LogAnd) {
			next();
			left = new BinaryExpr(left, TypeOp.Type.LogAnd, eqExpr());
		}
		return left;
	}

	// EqExpr ::= RelExpr (( "==" | "!=" ) RelExpr)?.
	private Expr eqExpr() throws IOException {
		Expr left = relExpr();
		if (type() == Type.Eq) {
			next();
			left = new BinaryExpr(left, TypeOp.Type.Eq, relExpr());
		}
		if (type() == Type.Not) {
			next();
			left = new BinaryExpr(left, TypeOp.Type.Not, relExpr());
		}
		return left;
	}

	// RelExpr ::= AddExpr (( "<" | "<=" | ">" | ">=" ) AddExpr)?.
	private Expr relExpr() throws IOException {
		Expr left = addExpr();
		if (type() == Type.Gt) {
			next();
			left = new BinaryExpr(left, TypeOp.Type.Gt, addExpr());
		}
		if (type() == Type.Ge) {
			next();
			left = new BinaryExpr(left, TypeOp.Type.Ge, addExpr());
		}
		if (type() == Type.Lt) {
			next();
			left = new BinaryExpr(left, TypeOp.Type.Lt, addExpr());
		}
		if (type() == Type.Le) {
			next();
			left = new BinaryExpr(left, TypeOp.Type.Le, addExpr());
		}
		return left;
	}

	// AddExpr ::= MulExpr (( "+" | "-" ) MulExpr)*.
	private Expr addExpr() throws IOException {
		Expr left = mulExpr();
		while (isAddOP()) {
			if (type() == Type.Plus) {
				next();
				left = new BinaryExpr(left, TypeOp.Type.Plus, mulExpr());
			}
			if (type() == Type.Minus) {
				next();
				left = new BinaryExpr(left, TypeOp.Type.Minus, mulExpr());
			}
		}
		return left;
	}

	// MulExpr ::= UnaryExpr (( "*" | "/" | "%" ) UnaryExpr)*.
	private Expr mulExpr() throws IOException {
		Expr left = unaryExpr();
		while (isMulOP()) {
			if (type() == Type.Times) {
				next();
				left = new BinaryExpr(left, TypeOp.Type.Times, unaryExpr());
			}
			if (type() == Type.Div) {
				next();
				left = new BinaryExpr(left, TypeOp.Type.Div, unaryExpr());
			}
			if (type() == Type.Pow) {
				next();
				left = new BinaryExpr(left, TypeOp.Type.Pow, unaryExpr());
			}
		}
		return left;
	}

	//UnaryExpr ::= ( "+" | "-" | "!" ) UnaryExpr
	//	    	| PostfixExpr.
	private Expr unaryExpr() throws IOException {
		Type oper = type();
//		next();
		if (oper == Type.Plus)
			return new UnaryExpr(TypeOp.Type.Plus, unaryExpr());
		else if (oper == Type.Minus)
			return new UnaryExpr(TypeOp.Type.Minus, unaryExpr());
		else if (oper == Type.Not)
			return new UnaryExpr(TypeOp.Type.Not, unaryExpr());
		return postFixExpr();
	}

	//PostfixExpr ::= PrimaryExpr Args*.
	private Expr postFixExpr() throws IOException {
		Expr expr = primaryExpr();
		while (type() == Type.OpenParen)
			expr = new InvokeExpr(expr, args());
		return expr;
	}
	
	//Args ::= "(" (SeqExpr ("," SeqExpr)*)? ")".
	private Args args() throws IOException {
		ArrayList<Expr> args = new ArrayList<Expr>();
		next();
		if(type() != Type.CloseParen) {
			args.add(seqExpr());
			while(type() == Type.Comma) {
				next();
				args.add(seqExpr());
			}
		}
		return new Args(args);
	}
	
	//PrimaryExpr ::= Ident
	//	   			| Num
	//				| Bool
	//	    		| Nil
	//	    		| String
	//	    		| Fun
	//	    		| "(" SeqExpr ")"
	//	    		| "print" | "println"
	//	    		| ("if" | "ifnot") SeqExpr "then" SeqExpr ("else" SeqExpr)? "fi"
	//	    		| ("while" | "whilenot") SeqExpr ("do" SeqExpr)? "od".
	private Expr primaryExpr() throws IOException {
		switch (type()) {
		//da continuare
		case Ident:
			return ident();
		case Num:
			return num(); 
		case OpenParen:
			next();
			return seqExpr();
		case Eos:
			System.exit(0);
		default:
			throw new ParserException("Mancata chiusura");
		}
	}
	
	private boolean isAssignOP() {
		switch (type()) {
		case Becomes:
		case PlusBecomes:
		case MinusBecomes:
		case TimesBecomes:
		case DivBecomes:
		case ModBecomes:
		case PowBecomes:
			return true;
		default:
			return false;
		}
	}


//	private Expr unExpr() throws IOException {
//		if (!isAddOP())
//			return primaryExpr();
//		Type oper = type();
//		next();
//		if (oper == Type.Minus) {
//			return new MinusUnExpr(unExpr());
//		} else {
//			return new PlusUnExpr(unExpr());
//		}
//	}


	// private Expr subExpr() {
	// // TODO Auto-generated method stub
	// return null;
	// }
	//
	// private Expr powExpr() {
	// // TODO Auto-generated method stub
	// return null;
	// }

	private Expr num() {
		return new Num(tokenizer.token().num());
	}

	private Expr ident() {
		return new Ident(tokenizer.token().name());
	}

	private Type type() {
		return tokenizer.token().type();
	}

	private void prev() {
		tokenizer.prev();
	}

	private void next() throws IOException {
		tokenizer.next();
	}

	private boolean isInFollowsAssignExpr() {
		switch (type()) {
		case Semicolon:
		case CloseParen:
		case Eos:
			return true;
		default:
			return false;
		}
	}

	private boolean isAddOP() {
		switch (type()) {
		case Plus:
		case Minus:
			return true;
		default:
			return false;
		}
	}

	private boolean isMulOP() {
		switch (type()) {
		case Times:
		case Div:
		case Pow:
			return true;
		default:
			return false;
		}
	}

	// private boolean isParenOP() {
	// return false;
	// }

}